﻿using System;
using System.Collections.Generic;
using System.Net;
using System.Diagnostics;

namespace Twitter
{
	/// <summary>
	/// Class responsible for retrieving incoming messages and followers, 
	/// establishing relationships and calling message processing providers 
	/// to perform actions on external systems.
	/// </summary>
	public class BotProcessor
	{
		IGreeter greeter;
		IMessageProcessor defaultProcessor;
		IEnumerable<IMessageProcessor> processors;
		IBotClient client;
		MessageTextNormalizer messageNormalizer = new MessageTextNormalizer();

		/// <summary>
		/// Initializes the processor.
		/// </summary>
		/// <param name="botUser">User name of the bot.</param>
		/// <param name="botPassword">Password of the bot.</param>
		/// <param name="greeter"><see cref="IGreeter"/> component to use to welcome new friends of the bot.</param>
		/// <param name="processors">List of message processors for new direct messages sent to the bot by its friends.</param>
		public BotProcessor(string botUser, string botPassword,
			IGreeter greeter,
			IMessageProcessor defaultProcessor,
			params IMessageProcessor[] processors)
			: this(botUser, botPassword, greeter, defaultProcessor, (IEnumerable<IMessageProcessor>)processors)
		{
		}

		/// <summary>
		/// Initializes the processor.
		/// </summary>
		/// <param name="botClient">Bot client to use for the process.</param>
		/// <param name="greeter"><see cref="IGreeter"/> component to use to welcome new friends of the bot.</param>
		/// <param name="defaultProcessor">Default message processing component.</param>
		/// <param name="processors">List of message processors for new direct messages sent to the bot by its friends.</param>
		public BotProcessor(IBotClient botClient,
			IGreeter greeter,
			IMessageProcessor defaultProcessor,
			params IMessageProcessor[] processors)
			: this(botClient, greeter, defaultProcessor, (IEnumerable<IMessageProcessor>)processors)
		{
		}

		/// <summary>
		/// Initializes the processor.
		/// </summary>
		/// <param name="botUser">User name of the bot.</param>
		/// <param name="botPassword">Password of the bot.</param>
		/// <param name="greeter"><see cref="IGreeter"/> component to use to welcome new friends of the bot.</param>
		/// <param name="processors">List of message processors for new direct messages sent to the bot by its friends.</param>
		public BotProcessor(string botUser, string botPassword,
			IGreeter greeter,
			IMessageProcessor defaultProcessor,
			IEnumerable<IMessageProcessor> processors)
			: this(new BotClient(new NetworkCredential(botUser, botPassword)),
				greeter, defaultProcessor, (IEnumerable<IMessageProcessor>)processors)
		{
			Tracer.TraceInformation(typeof(BotProcessor), "Initializing bot with user={0}, password={1}", botUser, botPassword);
		}

		/// <summary>
		/// Initializes the processor.
		/// </summary>
		/// <param name="botClient">Bot client to use for the process.</param>
		/// <param name="greeter"><see cref="IGreeter"/> component to use to welcome new friends of the bot.</param>
		/// <param name="processors">List of message processors for new direct messages sent to the bot by its friends.</param>
		public BotProcessor(IBotClient botClient,
			IGreeter greeter,
			IMessageProcessor defaultProcessor,
			IEnumerable<IMessageProcessor> processors)
		{
			this.greeter = greeter;
			this.defaultProcessor = defaultProcessor;
			this.processors = processors;
			this.client = botClient;
		}

		/// <summary>
		/// Runs the bot, making new friends and processing their messages.
		/// </summary>
		public void Run()
		{
			Run(true);
		}

		/// <summary>
		/// Testing overload that avoids looking for new friends.
		/// </summary>
		internal void Run(bool makeFriends)
		{
			try
			{
				if (makeFriends)
				{
					MakeFriendsAndGreet();
				}
				ProcessMessages();

			}
			catch (Exception ex)
			{
				Tracer.TraceError(typeof(BotProcessor), ex, "Run failed");
			}
		}

		private void MakeFriendsAndGreet()
		{
			var newFriends = client.Friendship.MakeFriends();

			foreach (var newFriend in newFriends)
			{

				var introMessage = new Message { Sender = new User { Name = newFriend }, Recipient = new User { Name = newFriend }, Text = "follow" };
				Tracer.TraceInformation(typeof(BotProcessor), "Invoking greeter {0} for new user {1}", greeter.GetType().FullName, newFriend);

				try
				{
					greeter.Greet(client, newFriend);
				}
				catch (Exception ex)
				{
					TwitterMessageLogger.AddLog(introMessage.Text, null, greeter.GetType().Name, ex, introMessage.Sender.ScreenName);
					Tracer.TraceError(typeof(BotProcessor), ex, "Greeer {0} failed");
				}
			}
		}

		private void ProcessMessages()
		{
			foreach (var message in client.Messages.GetAll())
			{
				messageNormalizer.NormalizeWhitespaceAndCasing(message);

				bool anyoneCouldProcess = false;

				Tracer.TraceInformation(typeof(BotProcessor), "Processing message: Id={0}, Text={1}, Sender={2}",
					message.Id, message.Text, message.Sender.ScreenName);


				foreach (var processor in processors)
				{
					Tracer.TraceInformation(typeof(BotProcessor), "Invoking processor {0}", processor.GetType().FullName);

					bool couldProcess = false;

					try
					{
						couldProcess = processor.CanProcess(message);

						if (couldProcess)
						{
							anyoneCouldProcess = true;
							RunNextProcessor shouldRunNext = processor.Process(client, message);
							if (shouldRunNext == RunNextProcessor.No)
								break;
						}
					}
					catch (Exception ex)
					{
						Tracer.TraceError(typeof(BotProcessor), ex, "Processor {0} failed", processor.GetType().FullName);
						if (couldProcess)
						{
							ErrorMessageSender.SendProcessingErrorMessage(client, message, processor, ex);
							BotProcessor.MessageUnderstoodWithError(client, message);
						}
						else
						{
							TwitterMessageLogger.AddLog(message.Text, null, processor.GetType().Name, ex, message.Sender.ScreenName);
						}
					}
				}
				if (!anyoneCouldProcess)
				{
					try
					{
						defaultProcessor.Process(client, message);
					}
					catch (Exception ex)
					{
						TwitterMessageLogger.AddLog(message.Text, null, defaultProcessor.GetType().Name, ex, message.Sender.ScreenName);
					}
				}
			}
		}

		private static void MessageUnderstoodWithError(IBotClient client, Message message)
		{
			//TODO: we should decide if we want to process this in a fashionable way.
			//maybe we could have a different error message queue that from time to time 
			//we try again to parse, we should save the date of the messages to avoid 
			//issues with time related messages like user location updates. (ndt)
			BotProcessor.MessageProcessed(client, message);
		}

		/// <summary>
		/// Helper function that do logging and delete message.
		/// </summary>
		/// <param name="message"></param>
		public static void MessageProcessed(IBotClient client, Message message)
		{
			Tracer.TraceInformation(typeof(BotProcessor), "Message processing successful. Deleting message Id={0}", message.Id);

			try
			{
				client.Messages.Delete(message.Id);
				Tracer.TraceInformation(typeof(BotProcessor), "Message Id={0} deleted", message.Id);
			}
			catch (Exception ex)
			{
				Tracer.TraceError(typeof(BotProcessor), ex, "Failed to delete message: Id={0}, Text={1}, Sender={2}",
					message.Id, message.Text, message.Sender.ScreenName);
			}
		}
	}
}
